﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MacomberMapSystem.Common.CIM;
using System.Xml;
using MacomberMapSystem.Common.Database;
using System.Drawing;
using MacomberMapSystem.Common.Serialization;
using MacomberMapSystem.Common.User_Interfaces.One_Lines;
using System.Drawing.Drawing2D;
using MacomberMapSystem.Common.Internals;

namespace MacomberMapSystem.Common.Exports
{
    /// <summary>
    /// This class is responsible for validating a one-line's topology
    /// </summary>
    public static class MM_OneLine_Validation
    {
        /// <summary>
        /// Run through the topology, to locate any errors
        /// </summary>
        /// <param name="xOneLine">The one-line in question</param>
        /// <param name="CIM">The CIM database to be used</param>
        /// <param name="OLExport">Our needed information for a one-line export</param>
        /// <param name="Errors">The number of found errors</param>
        /// <param name="Warnings">The number of found warnings</param>
        public static string ValidateTopology(XmlElement xOneLine, MM_Database_Model CIM, MM_Oneline_Export OLExport, out int Errors, out int Warnings)
        {
            DateTime StartTime = DateTime.Now;
            Errors = 0;
            Warnings = 0;
            if (OLExport.BaseElement == null)
            {
                //throw new InvalidOperationException("No base substation");
                Errors = 1;
                return "No base substation";
            }
            
            StringBuilder sB = new StringBuilder();
            sB.AppendLine("Topology validation ran on " + DateTime.Now.ToString());
            sB.AppendLine("Base element: " + OLExport.BaseElement.TypeName.Split(':')[1] + " " + OLExport.BaseElement.Name);

            //Use the new approach

            //Now, build our full list of elements that we should be expecting in the topology
            List<XmlElement> PositionXml = new List<XmlElement>();
            List<Object> PositionLocation = new List<object>();
            List<Object[]> PositionElement = new List<Object[]>();


            Dictionary<CIM_Element, CIM_Element[]> LinkedElements = new Dictionary<CIM_Element, CIM_Element[]>();
            List<CIM_Element> OtherElems = new List<CIM_Element>();
            Dictionary<CIM_Element, bool> NodeElemMatches = new Dictionary<CIM_Element, bool>();



            //Now, build our topology linkages, rolling transformer windings up to 

            foreach (CIM_Element cElem in OLExport.ElementsByRdfID.Values)
            {
                OtherElems.Clear();
                if (cElem.TypeName == "cim:ConnectivityNode")
                {
                    foreach (CIM_Element oElem in cElem.Elements)
                        if (OLExport.ElementsByRdfID.ContainsKey(oElem.rdfID))
                            if (oElem.TypeName == "cim:TransformerWinding")
                                OtherElems.Add(oElem.ParseSingle("cim:PowerTransformer", null));
                            else
                                OtherElems.Add(oElem);
                    /*foreach (CIM_Element oElem in cElem.Nodes)
                        if (ElementsByRdfID.ContainsKey(oElem.rdfID))
                            OtherElems.Add(oElem);*/
                }
                else if (cElem.TypeName == "cim:PowerTransformer")
                    OtherElems.AddRange(cElem.Elements);

                LinkedElements.Add(cElem, OtherElems.ToArray());
            }

            //Now, let's go through every element & node, and link them to XML.
            // foreach (String ValToCheck in "/One_Line/Elements/*,/One_Line/Elements/Transformer/Winding,/One_Line/Nodes/*,/One_Line/Nodes/Node/*,//PricingVector".Split(','))            

            foreach (String ValToCheck in "/One_Line/Elements/*,/One_Line/Nodes/Node/*/PricingVector,/One_Line/Nodes/*".Split(','))
                foreach (XmlElement xElem in xOneLine.SelectNodes(ValToCheck))
                {
                    //First, try and locate our element
                    CIM_Element FoundElem = null;
                    if (xElem.HasAttribute("rdfID"))
                        OLExport.ElementsByRdfID.TryGetValue(new CIM_RdfID(xElem.Attributes["rdfID"].Value), out FoundElem);
                    if (FoundElem == null && xElem.HasAttribute("BaseElement.TEID"))
                        OLExport.ElementsByTEID.TryGetValue(Convert.ToUInt64(xElem.Attributes["BaseElement.TEID"].Value), out FoundElem);

                    //If we haven't found an element, flag the error
                    if (FoundElem == null)
                    {
                        CIM_Element FoundElem2 = CIM.FromRdfID(xElem.Attributes["rdfID"].Value);
                        if (FoundElem2 != null)
                            sB.AppendLine(String.Format("ERR {0:000}: {1} {2} is referenced in the one-line, but not in topology. It's seen as {3} {4} in the one-line.", ++Errors, FoundElem2.ElemType, FoundElem2.Name, xElem.Name, xElem.Attributes["BaseElement.Name"].Value));
                        else
                            sB.AppendLine(String.Format("ERR {0:000}: {1} {2} is referenced in the one-line, but not in topology.", ++Errors, xElem.Name, xElem.Attributes["BaseElement.Name"].Value));
                    }

                    //If we've found it, but it doesn't belong, flag it
                    else if (!LinkedElements.ContainsKey(FoundElem))
                        sB.AppendLine(String.Format("ERR {0:000}: {1} {2} is referenced in the one-line, but not in topology.", ++Errors, xElem.Name, xElem.Attributes["BaseElement.Name"].Value));

                    //If it's referenced more than once, flag it
                    // else if (OLExport.MappedElements.ContainsKey(FoundElem))
                    //   sB.AppendLine(String.Format("ERR {0:000}: {1} {2} is referenced more than once in the one-line", ++Errors, xElem.Name, xElem.Attributes["BaseElement.Name"].Value));
                    else if (FoundElem.ElemType != xElem.Name)
                        if (FoundElem.Name.Equals(xElem.Attributes["BaseElement.Name"].Value, StringComparison.CurrentCultureIgnoreCase))
                            sB.AppendLine(String.Format("ERR {0:000}: The one-line treats element with name {1} as a {2}, while CIM as a {3}.", ++Errors, FoundElem.Name, xElem.Name, FoundElem.ElemType));
                        else
                            sB.AppendLine(String.Format("ERR {0:000}: The one-line treats element with TEID {1} as a {2} (named {3}), while CIM as a {4} (named {5}).",
                                ++Errors, xElem.Attributes["BaseElement.TEID"].Value, xElem.Name, xElem.Attributes["BaseElement.Name"].Value, FoundElem.ElemType, FoundElem.Name));
                    //else
                 //       OLExport.MappedElements.Add(FoundElem, xElem);

                    //Check for our invalid orientation
                    MM_OneLine_Element.enumElemTypes ElemType = (MM_OneLine_Element.enumElemTypes)Enum.Parse(typeof(MM_OneLine_Element.enumElemTypes), xElem.Name);
                    MM_OneLine_Element.enumOrientations Orientation = MM_OneLine_Element.enumOrientations.Unknown;
                    if (xElem.HasAttribute("Orientation"))
                        Orientation = (MM_OneLine_Element.enumOrientations)Enum.Parse(typeof(MM_OneLine_Element.enumOrientations), xElem.Attributes["Orientation"].Value);
                    

                    if (FoundElem != null && !MM_OneLine_Element.AllowedOrientations(ElemType, FoundElem.TypeName != "cim:SeriesCompensator").Contains(Orientation))
                    {
                        StringBuilder sOrient = new StringBuilder();
                        foreach (MM_OneLine_Element.enumOrientations o2 in MM_OneLine_Element.AllowedOrientations(ElemType,FoundElem.TypeName != "cim:SeriesCompensator"))
                            sOrient.Append((sOrient.Length == 0 ? "" : ", ") + o2.ToString());
                        sB.AppendLine(String.Format("ERR {0:000}: {1} {2} is {3}, but allowed orientations are {4}.", ++Errors, xElem.Name, xElem.Attributes["BaseElement.Name"].Value, Orientation.ToString(), sOrient.ToString()));
                    }
                }

            //Flag errors on elements not appearing
            foreach (CIM_Element Elem in OLExport.ElementsByRdfID.Values)
            {
                if (Elem.TypeName != "cim:TransformerWinding" && !OLExport.MappedElements.ContainsKey(Elem))
                    sB.AppendLine(String.Format("ERR {0:000}: {1} {2} is referenced in topology, but not in the one-line.", ++Errors, Elem.ElemType, Elem.Name));
                if (String.IsNullOrEmpty(Elem.TEID))
                    sB.AppendLine(String.Format("ERR {0:000}: {1} {2} is missing a TEID.", ++Errors, Elem.ElemType, Elem.Name));
            }

            //Now, make sure our labels and connectivities are properly set up
            foreach (KeyValuePair<CIM_Element, XmlElement> kvp in OLExport.MappedElements)
            {
                bool NeedsLabel = kvp.Key.TypeName != "cim:ConnectivityNode" || kvp.Key.ParseSingle("cim:Terminal>BusbarSection", CIM) != null;
                bool NeedsRN = kvp.Key.TypeName == "cim:ConnectivityNode" && kvp.Key.ParseSingle("etx:ElectricalBus<etx:ResourceNode", CIM) != null;
                XmlElement xDesc = kvp.Value["Descriptor"];
                XmlElement xSDesc = kvp.Value["SecondaryDescriptor"];
                Rectangle ElemRect = MM_Serializable<Rectangle>.ConvertObject(kvp.Value.Attributes["Bounds"].Value, null);
                if (kvp.Value.Name == "PricingVector")
                    AddElementPosition(PositionXml, PositionLocation, PositionElement, kvp.Value, ElemRect, kvp.Key, kvp.Key.Nodes[0]);
                else
                    AddElementPosition(PositionXml, PositionLocation, PositionElement, kvp.Value, ElemRect, kvp.Key);

                //Go searching for our descriptor, if possible
                if (xDesc == null)
                    xDesc = kvp.Value.SelectSingleNode("*/PokePoint/Descriptor") as XmlElement;


                if (xDesc != null)
                {
                    Rectangle BaseRect = MM_Serializable<Rectangle>.ConvertObject(xDesc.Attributes["Bounds"].Value, null);
                    AddElementPosition(PositionXml, PositionLocation, PositionElement, xDesc, BaseRect, kvp.Key);
                    //Determine our SCADA & RTNET points
                    foreach (MM_OneLine_Value Val in MM_OneLine_Element.BuildExportPointsWithLocations(kvp.Value, MM_OneLine_Element.BuildExportPoints(kvp.Key, OLExport.BaseElement, false, xDesc), ElemRect, BaseRect))
                    {
                        Val.AssociatedElement = kvp.Key;
                        AddElementPosition(PositionXml, PositionLocation, PositionElement, xDesc, Val.Bounds, Val);
                    }
                }
                if (xSDesc != null)
                    AddElementPosition(PositionXml, PositionLocation, PositionElement, xSDesc, MM_Serializable<Rectangle>.ConvertObject(xSDesc.Attributes["Bounds"].Value, null), kvp.Key);

                if (NeedsLabel && xDesc == null)
                    sB.AppendLine(String.Format("WRN {0:000}: {1} {2} ({3}) does not have a descriptor.", ++Warnings, kvp.Key.ElemType, kvp.Value.Attributes["BaseElement.Name"].Value, kvp.Key.VoltageLevel.Name));
                else if (!NeedsLabel && xDesc != null)
                    sB.AppendLine(String.Format("WRN {0:000}: {1} {2} ({3}) has an unnecessary descriptor.", ++Warnings, kvp.Key.ElemType, kvp.Value.Attributes["BaseElement.Name"].Value, kvp.Key.VoltageLevel.Name));
                if (NeedsRN && xSDesc == null)
                    sB.AppendLine(String.Format("WRN {0:000}: {1} {2} ({3}) does not have a secondary descriptor.", ++Warnings, kvp.Key.ElemType, kvp.Value.Attributes["BaseElement.Name"].Value, kvp.Key.VoltageLevel.Name));
                else if (!NeedsRN && xSDesc != null)
                    sB.AppendLine(String.Format("WRN {0:000}: {1} {2} ({3}) has an unnecessary secondary descriptor.", ++Warnings, kvp.Key.ElemType, kvp.Value.Attributes["BaseElement.Name"].Value, kvp.Key.VoltageLevel.Name));


                CIM_Element[] LinkedElems;

                //If we're a breaker or switch, check our normal open status. If we're a node, make sure our connectivity is set up properly.
                if ((kvp.Value.Name == "Switch" || kvp.Value.Name == "Breaker") && XmlConvert.ToBoolean(kvp.Key["cim:Switch.normalOpen"]) != (kvp.Value.Attributes["Opened"].Value == "Checked"))
                    sB.AppendLine(String.Format("ERR {0:000}: The normal open status of {1} {2} does not match between the one-line and CIM model.", ++Errors, kvp.Key.ElemType, kvp.Key.Name));
                else if (kvp.Key.TypeName == "cim:PowerTransformer")
                {
                    String Orientation = kvp.Value.Attributes["Orientation"].Value;
                    List<CIM_Element> Windings = new List<CIM_Element>(LinkedElements[kvp.Key]);
                    foreach (XmlElement xWinding in kvp.Value.SelectNodes("Winding"))
                    {
                        CIM_Element cWinding = null;
                        if (xWinding.HasAttribute("rdfID"))
                            OLExport.ElementsByRdfID.TryGetValue(new CIM_RdfID(xWinding.Attributes["rdfID"].Value), out cWinding);
                        if (cWinding == null && xWinding.HasAttribute("BaseElement.TEID"))
                            OLExport.ElementsByTEID.TryGetValue(Convert.ToUInt64(xWinding.Attributes["BaseElement.TEID"].Value), out cWinding);
                        if (cWinding == null)
                            sB.AppendLine(String.Format("ERR {0:000}: {1} {2} references a winding with TEID {3} in the one-line, but it was not found in topology.", ++Errors, kvp.Key.ElemType, kvp.Key.Name, (xWinding.HasAttribute("BaseElement.TEID") ? xWinding.Attributes["BaseElement.TEID"].Value : " unknown ")));
                        else if (!Windings.Remove(cWinding))
                            sB.AppendLine(String.Format("ERR {0:000}: {1} {2} references {3} {4} in the one-line, but not in topology.", ++Errors, kvp.Key.ElemType, kvp.Key.Name, cWinding.ElemType, cWinding.Name));

                        //Make sure we don't have a horizontal/vertical mismatch between XF and windings
                        String WindingOrientation = xWinding.Attributes["Orientation"].Value;
                        if ((Orientation == "Horizontal" && (WindingOrientation == "Up" || WindingOrientation == "Down")) || (Orientation == "Vertical" && (WindingOrientation == "Left" || WindingOrientation == "Right")))
                            sB.AppendLine(String.Format("ERR {0:000}: Transformer/winding orientation mismatch: {1} vs. {2} for Transformer {3} ({4})", ++Errors, Orientation, WindingOrientation, kvp.Value.Attributes["BaseElement.Name"].Value, kvp.Key.VoltageLevel.Name));                         

                        //Check the orientation between our windings and their target nodes.
                        CIM_Element AssocNode = cWinding.ParseSingle("cim:Terminal>cim:ConnectivityNode", CIM);
                        XmlElement xNode;
                        if (!OLExport.MappedElements.TryGetValue(AssocNode, out xNode))
                            sB.AppendLine(String.Format("ERR {0:000}: Transformer/winding {1} ({2}) points to node {3}, which is not in the one-line.", ++Errors, kvp.Value.Attributes["BaseElement.Name"].Value, kvp.Key.VoltageLevel.Name, AssocNode.Name));
                        else
                        {
                            //Determine the relationship between the winding and the node/latest poke point
                            Rectangle XFRect = MM_Serializable<Rectangle>.ConvertObject(kvp.Value.Attributes["Bounds"].Value,null);
                            Rectangle XFWRect = MM_Serializable<Rectangle>.ConvertObject(xWinding.Attributes["Bounds"].Value,null);
                            XFWRect.X += XFRect.X;
                            XFWRect.Y += XFRect.Y;
                            XmlNode XFPointer =  xNode.SelectSingleNode("Transformer[@TEID='" + kvp.Key.TEID + "']");
                            if (XFPointer == null)
                                sB.AppendLine(String.Format("ERR {0:000}: Transformer winding {1} to node {2} ({3}) linkage not found", ++Errors, xWinding.Attributes["BaseElement.Name"].Value, xNode.Attributes["BaseElement.Name"].Value, AssocNode.VoltageLevel.Name));
                            else
                            {
                                XmlNodeList xNodePokes = XFPointer.SelectNodes("PokePoint");
                                Rectangle NodeRect = xNodePokes.Count == 0 ? MM_Serializable<Rectangle>.ConvertObject(xNode.Attributes["Bounds"].Value, null) : MM_Serializable<Rectangle>.ConvertObject(xNodePokes[xNodePokes.Count - 1].Attributes["Bounds"].Value, null);
                                String WindingToNodeOrientation = DetermineOrientation(NodeRect, XFWRect).ToString();
                                if (WindingOrientation != WindingToNodeOrientation)
                                    sB.AppendLine(String.Format("ERR {0:000}: Transformer winding {1} to node {2} ({3}) orientation mismatch: {4} vs. {5}", ++Errors, xWinding.Attributes["BaseElement.Name"].Value, xNode.Attributes["BaseElement.Name"].Value, AssocNode.VoltageLevel.Name, WindingOrientation, WindingToNodeOrientation));
                            }
                        }




                    }
                    foreach (CIM_Element cWinding in Windings)
                        sB.AppendLine(String.Format("ERR {0:000}: {1} {2} references {3} {4} in topology, but not in the one-line.", ++Errors, kvp.Key.ElemType, kvp.Key.Name, cWinding.ElemType, cWinding.Name));
                }
                else if (kvp.Key.TypeName == "cim:ConnectivityNode")
                    if (LinkedElements.TryGetValue(kvp.Key, out LinkedElems))
                    {
                        //First, build our list of matches
                        NodeElemMatches.Clear();
                        foreach (CIM_Element lElem in LinkedElems)
                            NodeElemMatches.Add(lElem, false);

                        //Now, check our connectivity
                        foreach (XmlElement xNodeCh in kvp.Value.ChildNodes)
                            if (!xNodeCh.Name.EndsWith("Descriptor"))
                            {
                                CIM_Element NodeElem = null;
                                if (xNodeCh.HasAttribute("rdfID"))
                                    OLExport.ElementsByRdfID.TryGetValue(new CIM_RdfID(xNodeCh.Attributes["rdfID"].Value), out NodeElem);
                                else if (xNodeCh.HasAttribute("TEID"))
                                    if (NodeElem == null)
                                        OLExport.ElementsByTEID.TryGetValue(Convert.ToUInt64(xNodeCh.Attributes["TEID"].Value), out NodeElem);

                                if (NodeElem == null || !NodeElemMatches.ContainsKey(NodeElem))
                                    if (xNodeCh.HasAttribute("TEID"))
                                        sB.AppendLine(String.Format("ERR {0:000}: {1} with TEID {2:#,##0} is referenced by {3} {4} in the one-line, but not in topology.", ++Errors, xNodeCh.Name, xNodeCh.Attributes["TEID"].Value, kvp.Key.ElemType, kvp.Key.Name));
                                    else
                                        sB.AppendLine(String.Format("ERR {0:000}: {1} with TEID {2:#,##0} is referenced by {3} {4} in the one-line, but not in topology.", ++Errors, xNodeCh.Name, xNodeCh.Attributes["BaseElement.TEID"].Value, kvp.Key.ElemType, kvp.Key.Name));
                                else
                                {
                                    NodeElemMatches[NodeElem] = true;
                                    AddElementPosition(PositionXml, PositionLocation, PositionElement, xNodeCh, MM_OneLine_Element.BuildNodePath(kvp.Value, xNodeCh), kvp.Key, NodeElem);
                                    //Also, add the bounds for every sub-child
                                    foreach (XmlElement xPoke in xNodeCh.ChildNodes)
                                        if (xPoke.Name == "PokePoint")
                                            if ((!xPoke.HasAttribute("IsJumper") || !XmlConvert.ToBoolean(xPoke.Attributes["IsJumper"].Value)))
                                                AddElementPosition(PositionXml, PositionLocation, PositionElement, xPoke, MM_Serializable<Rectangle>.ConvertObject(xPoke.Attributes["Bounds"].Value, null), kvp.Key, NodeElem);
                                            else if (xPoke.Attributes["Orientation"].Value == "Horizontal" && !xPoke.Attributes["Bounds"].Value.TrimEnd(',').EndsWith(",35,11"))
                                                sB.AppendLine(String.Format("WRN {0:000}: Node {1} to {2} {3} ({5}) {6} jumper at {7} isn't sized properly.", ++Warnings, kvp.Value.Attributes["BaseElement.Name"].Value, xPoke.ParentNode.Name, NodeElem.LineName, xPoke.ParentNode.Attributes["TEID"].Value, NodeElem.VoltageLevel.Name, xPoke.Attributes["Orientation"].Value, xPoke.Attributes["Bounds"].Value));
                                            else if (xPoke.Attributes["Orientation"].Value == "Vertical" && !xPoke.Attributes["Bounds"].Value.TrimEnd(',').EndsWith(",11,35"))
                                                sB.AppendLine(String.Format("WRN {0:000}: Node {1} to {2} {3} ({5}) {6} jumper at {7} isn't sized properly.", ++Warnings, kvp.Value.Attributes["BaseElement.Name"].Value, xPoke.ParentNode.Name, NodeElem.LineName, xPoke.ParentNode.Attributes["TEID"].Value, NodeElem.VoltageLevel.Name, xPoke.Attributes["Orientation"].Value, xPoke.Attributes["Bounds"].Value));
                                }

                                //Also, check for pricing vectors below
                                foreach (XmlElement NodePV in xNodeCh.SelectNodes("PricingVector"))
                                {
                                    CIM_Element cPV = null;
                                    if (NodePV.HasAttribute("rdfID"))
                                        OLExport.ElementsByRdfID.TryGetValue(new CIM_RdfID(NodePV.Attributes["rdfID"].Value), out cPV);
                                    if (cPV == null)
                                        OLExport.ElementsByTEID.TryGetValue(Convert.ToUInt64(NodePV.Attributes["BaseElement.TEID"].Value), out cPV);

                                    if (cPV == null || NodeElem == null)
                                        sB.AppendLine(String.Format("ERR {0:000}: {1} with TEID {2:#,##0} is referenced by {3} {4} in the one-line, but not in topology.", ++Errors, NodePV.Name, NodePV.Attributes["BaseElement.TEID"].Value, kvp.Key.ElemType, kvp.Key.Name));
                                    else if (!NodeElemMatches.ContainsKey(cPV))
                                        if (xNodeCh.HasAttribute("TEID"))
                                            sB.AppendLine(String.Format("ERR {0:000}: {1} with TEID {2:#,##0} is referenced by {3} {4} in the one-line, but not in topology.", ++Errors, xNodeCh.Name, xNodeCh.Attributes["TEID"].Value, kvp.Key.ElemType, kvp.Key.Name));
                                        else
                                            sB.AppendLine(String.Format("ERR {0:000}: {1} with TEID {2:#,##0} is referenced by {3} {4} in the one-line, but not in topology.", ++Errors, xNodeCh.Name, xNodeCh.Attributes["BaseElement.TEID"].Value, kvp.Key.ElemType, kvp.Key.Name));
                                    //Make sure we're connected to the correct element
                                    else if (NodeElem != null && NodeElem.ElemType == "Transformer")
                                    {
                                        bool FoundXF = false;
                                        foreach (CIM_Element XFw in NodeElem.TransformerWinding)
                                            if (Array.IndexOf(cPV.Elements, XFw) != -1)
                                            {
                                                NodeElemMatches[cPV] = true;
                                                FoundXF = true;
                                            }
                                        if (!FoundXF)
                                        {
                                            CIM_Element[] PVElems = cPV.Elements;
                                            sB.AppendLine(String.Format("ERR {0:000}: {1} {2} is referenced between {3} {4} and {5} {6} in the one-line, but in topology it's between {7} {8} and {9} {10}.", ++Errors,
                                                cPV.ElemType, cPV.Name, kvp.Key.ElemType, kvp.Key.Name, NodeElem.ElemType, NodeElem.Name, PVElems[0].ElemType, PVElems[0].Name, PVElems[1].ElemType, PVElems[1].Name));
                                        }
                                    }
                                    else if (Array.IndexOf(cPV.Elements, NodeElem) == -1)
                                    {
                                        CIM_Element[] PVElems = cPV.Elements;
                                        sB.AppendLine(String.Format("ERR {0:000}: {1} {2} is referenced between {3} {4} and {5} {6} in the one-line, but in topology it's between {7} {8} and {9} {10}.", ++Errors,
                                            cPV.ElemType, cPV.Name, kvp.Key.ElemType, kvp.Key.Name, NodeElem.ElemType, NodeElem.Name, PVElems[0].ElemType, PVElems[0].Name, PVElems[1].ElemType, PVElems[1].Name));
                                    }
                                    else
                                    {
                                        //Find our element
                                        NodeElemMatches[cPV] = true;

                                        //Also, check our PV's orientation and IsPositive
                                        Dictionary<String, String> xAttr = cPV.Attributes;

                                        //Check the positive direction
                                        bool cIsPositive = XmlConvert.ToBoolean(cPV["etx:PricingVector.positiveFlowIn"]);
                                        bool xIsPositive = XmlConvert.ToBoolean(NodePV.Attributes["IsPositive"].Value);
                                        if (cIsPositive != xIsPositive)
                                            sB.AppendLine(String.Format("ERR {0:000} : {1} {2}'s vector has an opposite 'IsPositive' value from CIM.", ++Errors, NodePV.Name, NodePV.Attributes["BaseElement.Name"].Value));

                                        //Check the orientations
                                        try
                                        {
                                            double Dist, Angle, DeltaX, DeltaY;
                                            bool WithinX, WithinY;

                                            //Find our path back to the node - if we have a prior poke use that, otherwise use the node itself.
                                            Rectangle BoundsToNode, VectorBounds;
                                            if (NodePV.PreviousSibling != null)
                                                BoundsToNode = MM_Serializable<Rectangle>.ConvertObject(NodePV.PreviousSibling.Attributes["Bounds"].Value, null);
                                            else
                                                BoundsToNode = MM_Serializable<Rectangle>.ConvertObject(NodePV.ParentNode.ParentNode.Attributes["Bounds"].Value, null);

                                            VectorBounds = MM_Serializable<Rectangle>.ConvertObject(NodePV.Attributes["Bounds"].Value, null);
                                            ComputeDistanceAndAngle(VectorBounds, BoundsToNode, out Dist, out Angle, out DeltaX, out DeltaY, out WithinX, out WithinY);
                                            String Orientation;
                                            if (Math.Abs(DeltaX) > Math.Abs(DeltaY))
                                                Orientation = (DeltaX < 0 ? "Right" : "Left");
                                            else
                                                Orientation = (DeltaY < 0 ? "Up" : "Down");


                                            if (Orientation != NodePV.Attributes["Orientation"].Value)
                                                sB.AppendLine(String.Format("ERR {0:000} : {1} {2} should have its node's direction = {3}, but it's {4}. Note that direction is the path to the connectivity node.", ++Errors, NodePV.Name, NodePV.Attributes["BaseElement.Name"].Value, Orientation, NodePV.Attributes["Orientation"].Value));
                                        }
                                        catch (Exception ex)
                                        {
                                            sB.AppendLine(String.Format("ERR {0:000} : {1} {2} node direction couldn't be computed: {3}", ++Errors, NodePV.Name, NodePV.Attributes["BaseElement.Name"].Value, ex.Message));
                                        }

                                    }
                                }

                            }
                        foreach (KeyValuePair<CIM_Element, bool> kvp2 in NodeElemMatches)
                            if (!kvp2.Value)
                            {
                                String Elem2Name = kvp2.Key.Name;
                                String Elem1Name = kvp.Key.Name;

                                if (kvp2.Key.TypeName == "cim:ACLineSegment" || kvp2.Key.TypeName == "cim:SeriesCompensator")
                                {
                                    CIM_Element ParentLine = kvp2.Key.ParseSingle("cim:Line", null);
                                    Elem2Name = ParentLine.Name.ToUpper() + (ParentLine.InLinks.Length == 1 ? "" : kvp2.Key.Name.ToUpper());
                                }

                                if (kvp.Key.TypeName == "cim:ACLineSegment" || kvp.Key.TypeName == "cim:SeriesCompensator")
                                {
                                    CIM_Element ParentLine = kvp.Key.ParseSingle("cim:Line", null);
                                    Elem2Name = ParentLine.Name.ToUpper() + (ParentLine.InLinks.Length == 1 ? "" : kvp.Key.Name.ToUpper());
                                }

                                sB.AppendLine(String.Format("ERR {0:000}: {1} {2} is referenced by {3} {4} in the topology, but not in the one-line.", ++Errors, kvp2.Key.ElemType, Elem2Name, kvp.Key.ElemType, Elem1Name));
                            }

                    }
            }

            //Now, add any non-linked elements
            foreach (XmlElement xUnlinked in xOneLine.SelectNodes("/One_Line/Unlinked_Elements/*").OfType<XmlElement>())
            {                
                PositionElement.Add(new object[] {new MM_OneLine_Element(xUnlinked, CIM, null)});
                PositionXml.Add(xUnlinked);
                PositionLocation.Add(MM_Serializable<Rectangle>.ConvertObject(xUnlinked.Attributes["Bounds"].Value, CIM.Repository));
            }
            
            
            //Build our list of RAS gate information
            if (OLExport.RASGates.Count > 0)
            {
                XmlElement xRAS = xOneLine.OwnerDocument.CreateElement("RAS_Status_Panel");
                xRAS.Attributes.Append(xOneLine.OwnerDocument.CreateAttribute("Bounds")).Value = "0,48,160," + ((25 * OLExport.RASGates.Count) + 17).ToString();
                PositionElement.Add(new object[] { new MM_OneLine_Element(xRAS, CIM, null) });
                PositionXml.Add(xRAS);
                PositionLocation.Add(MM_Serializable<Rectangle>.ConvertObject(xRAS.Attributes["Bounds"].Value, CIM.Repository));
            }
                


            //Now, perform our analysis between all points
            PointF Intersection;
            for (int a = 0; a < PositionXml.Count; a++)
            {
                for (int b = a + 1; b < PositionXml.Count; b++)
                    if (!ElementEquals(PositionElement[a], PositionElement[b], PositionXml[a], PositionXml[b]))
                    {
                        Object obj1 = PositionLocation[a];
                        Object obj2 = PositionLocation[b];
                        bool Result;
                        if (obj1 is Rectangle && obj2 is Rectangle)
                            Result = MM_HitTesting.CheckIntersection((Rectangle)obj1, (Rectangle)obj2, out Intersection);
                        else if (obj1 is Rectangle && obj2 is GraphicsPath)
                            Result = MM_HitTesting.CheckIntersection((Rectangle)obj1, (GraphicsPath)obj2, out Intersection);
                        else if (obj2 is Rectangle && obj1 is GraphicsPath)
                            Result = MM_HitTesting.CheckIntersection((Rectangle)obj2, (GraphicsPath)obj1, out Intersection);
                        else if (obj1 is GraphicsPath && obj2 is GraphicsPath)
                            Result = MM_HitTesting.CheckIntersection((GraphicsPath)obj1, (GraphicsPath)obj2, out Intersection);
                        else
                            throw new InvalidOperationException("Don't know how to check for hit testing between two objects.");

                        if (Result)
                        {
                            //Find our names for our elements
                            List<String> IntersectingElems = new List<String>();    
                            foreach (int Pos in new int[] { a, b })
                            {
                                String OutStr = "";
                                if (PositionXml[Pos].Name == "Descriptor")
                                    OutStr = "Descriptor for ";
                                else if (PositionXml[Pos].Name == "SecondaryDescriptor")
                                    OutStr = "Secondary descriptor for ";
                                else if (PositionXml[Pos].Name == "PokePoint")
                                    if (PositionXml[Pos].HasAttribute("IsJumper") && XmlConvert.ToBoolean(PositionXml[Pos].Attributes["IsJumper"].Value))
                                        OutStr = "Jumper for ";
                                    else if (XmlConvert.ToBoolean(PositionXml[Pos].Attributes["IsVisible"].Value))
                                        OutStr = "Poke point for ";
                                    else
                                        OutStr = "Invisible poke point for ";

                                if (PositionElement[Pos].Length == 1 && PositionElement[Pos][0] is CIM_Element)
                                {
                                    CIM_Element Elem = PositionElement[Pos][0] as CIM_Element;
                                    if (Elem.TypeName == "cim:ACLineSegment" || Elem.TypeName == "cim:SeriesCompensator")
                                    {
                                        CIM_Element ParentLine = Elem.ParseSingle("cim:Line", null);
                                        IntersectingElems.Add(OutStr + Elem.ElemType + " " + (ParentLine.Name + (ParentLine.InLinks.Length == 1 ? "" : Elem.Name.ToUpper())));
                                    }
                                    else
                                        IntersectingElems.Add(OutStr + Elem.ElemType + " " + Elem.Name);
                                }
                                else if (PositionElement[Pos].Length == 1 && PositionElement[Pos][0] is MM_OneLine_Value)
                                {
                                    MM_OneLine_Value Val = PositionElement[Pos][0] as MM_OneLine_Value;
                                    if (Val.AssociatedElement.TypeName == "cim:ACLineSegment" || Val.AssociatedElement.TypeName == "cim:SeriesCompensator")
                                    {
                                        CIM_Element ParentLine = Val.AssociatedElement.ParseSingle("cim:Line", null);
                                        IntersectingElems.Add(Val.OutputMode.ToString() + " " + Val.ValueType.ToString() + " for " + Val.AssociatedElement.ElemType + " " + (ParentLine.Name + (ParentLine.InLinks.Length == 1 ? "" : Val.AssociatedElement.Name.ToUpper())));
                                    }
                                    else
                                        IntersectingElems.Add(Val.OutputMode.ToString() + " " + Val.ValueType.ToString() + " for " + Val.AssociatedElement.ElemType + " " + Val.AssociatedElement.Name);
                                }
                                else if (PositionElement[Pos].Length == 1 && PositionElement[Pos][0] is MM_OneLine_Element)
                                    IntersectingElems.Add((PositionElement[Pos][0] as MM_OneLine_Element).Definition.Name);
                                else
                                {
                                    CIM_Element Elem1 = PositionElement[Pos][0] as CIM_Element;
                                    CIM_Element Elem2 = PositionElement[Pos][1] as CIM_Element;
                                    if (PositionXml[Pos].Name == "PricingVector")
                                        IntersectingElems.Add(OutStr + Elem1.ElemType + " " + Elem1.Name);
                                    else
                                        IntersectingElems.Add(OutStr + Elem1.ElemType + " " + Elem1.Name + " to " + Elem2.ElemType + " " + Elem2.Name);
                                }
                            }
                            sB.AppendLine(String.Format("HTW {0:000}: {1} intersects with {2} at {3},{4}", ++Warnings, IntersectingElems[0], IntersectingElems[1], Intersection.X, Intersection.Y));
                        }
                    }
                if (PositionLocation[a] is GraphicsPath)
                    foreach (KeyValuePair<PointF, PointF> NodePath in MM_HitTesting.ExtractLines(PositionLocation[a] as GraphicsPath))
                        if (NodePath.Key.X != NodePath.Value.X && NodePath.Key.Y != NodePath.Value.Y)
                        {
                            CIM_Element FromDiag = PositionElement[a][0] as CIM_Element;
                            CIM_Element ToDiag = PositionElement[a][1] as CIM_Element;

                            sB.AppendLine(String.Format("NOD {0:000}: The path from {1} {2} to {3} {4} appears to be diagonal.", ++Warnings, FromDiag.ElemType, FromDiag.Name, ToDiag.ElemType, ToDiag.Name));
                        }
            }

            sB.AppendLine();
            sB.AppendLine(String.Format("Topology validation completed in {0}, with {1:#,##0} errors and {2:#,##0} warnings", DateTime.Now - StartTime, Errors, Warnings));
            return sB.ToString();
        }

        /// <summary>
        /// Determine the orientation from one rectangle to another
        /// </summary>
        /// <param name="SourceRect"></param>
        /// <param name="TargetRect"></param>
        /// <returns></returns>
        public static MM_OneLine_Element.enumOrientations DetermineOrientation(Rectangle SourceRect, Rectangle TargetRect)
        {
            double Dist, Angle, DeltaX, DeltaY;
            bool WithinX, WithinY;
            ComputeDistanceAndAngle(SourceRect, TargetRect, out Dist, out Angle, out DeltaX, out DeltaY, out WithinX, out WithinY);

            if (Math.Abs(DeltaX) > Math.Abs(DeltaY))
                return DeltaX > 0 ? MM_OneLine_Element.enumOrientations.Right : MM_OneLine_Element.enumOrientations.Left;
            else
                return DeltaY > 0 ? MM_OneLine_Element.enumOrientations.Down : MM_OneLine_Element.enumOrientations.Up;
        }

        /// <summary>
        /// Determine whether two elements are equivalent
        /// </summary>
        /// <param name="Elem1"></param>
        /// <param name="Elem2"></param>
        /// <param name="xElem1"></param>
        /// <param name="xElem2"></param>
        /// <returns></returns>        
        private static bool ElementEquals(Object[] Elem1, Object[] Elem2, XmlElement xElem1, XmlElement xElem2)
        {
            //First, perform our pricing vector check
            if (xElem1.Name == "PricingVector" && Elem2.Length == 2)
                return Array.IndexOf(Elem2, Elem1[1]) != -1;
            else if (xElem2.Name == "PricingVector" && Elem1.Length == 2)
                return Array.IndexOf(Elem1, Elem2[1]) != -1;


            //Determine our two element types
            int Elem1Type = 0, Elem2Type = 0;
            if (xElem1.Name.EndsWith("Descriptor"))
                Elem1Type = 1;
            else if (Elem1.Length == 2)
                Elem1Type = 3;
            else if (Elem1[0] is MM_OneLine_Value)
                Elem1Type = 2;
            else if (Elem1[0] is MM_OneLine_Element)
                Elem1Type = 4;

            if (xElem2.Name.EndsWith("Descriptor"))
                Elem2Type = 1;
            else if (Elem2.Length == 2)
                Elem2Type = 3;
            else if (Elem2[0] is MM_OneLine_Value)
                Elem2Type = 2;
            else if (Elem2[0] is MM_OneLine_Element)
                Elem2Type = 4;


            String Actions = "CFFWFCCFFCRFWFFEFFFFE";
            Char Action = Actions[(Elem2Type * 4) + Elem1Type];
            if (Action == 'C')
            {
                CIM_Element c1 = null, c2 = null;
                if (Elem1[0] is CIM_Element)
                    c1 = Elem1[0] as CIM_Element;
                else if (Elem1[0] is MM_OneLine_Value)
                    c1 = (Elem1[0] as MM_OneLine_Value).AssociatedElement;                
                if (Elem2[0] is CIM_Element)
                    c2 = Elem2[0] as CIM_Element;
                else if (Elem2[0] is MM_OneLine_Value)
                    c2 = (Elem2[0] as MM_OneLine_Value).AssociatedElement;
                return c1 == c2;
            }
            else if (Action == 'F')
                return false;
            else if (Action == 'W' && Elem1.Length == 2)
                return Array.IndexOf(Elem1, Elem2[0]) != -1;
            else if (Action == 'W')
                return Array.IndexOf(Elem2, Elem1[0]) != -1;
            else if (Action == 'E')
                return Elem1[0] == Elem2[0];//&& Elem1[1] == Elem2[1];
            else
                throw new InvalidOperationException("Unknown element comparison!");
        }

        /// <summary>
        /// Add a descriptor to our collection
        /// </summary>
        /// <param name="xElem"></param>
        /// <param name="Elems"></param>
        /// <param name="BaseElement"></param>
        /// <param name="ParentElement"></param>
        private static void AddDescriptor(XmlElement xElem, Dictionary<MM_OneLine_Element, Rectangle> Elems, CIM_Element BaseElement, MM_OneLine_Element ParentElement)
        {
            if (xElem != null)
            {
                MM_OneLine_Element Desc = new MM_OneLine_Element(xElem, BaseElement.CIM, BaseElement);
                Desc.ParentElement = ParentElement;
                Desc.ExportValues = MM_OneLine_Element.BuildExportPointsWithLocations(xElem, MM_OneLine_Element.BuildExportPoints(Desc.CIM, BaseElement, Desc.ElemType == MM_OneLine_Element.enumElemTypes.SecondaryDescriptor, xElem), Desc.ParentElement.Bounds, Desc.Bounds);
                Elems.Add(Desc, Desc.OuterBounds);


            }
        }

        /// <summary>
        /// Compute the closest distance and angle between two points
        /// </summary>
        /// <param name="BaseRect">Our base rectangle</param>
        /// <param name="OtherRect">The rectangle for comparison</param>
        /// <param name="Dist">The distance between the two rectangles</param>
        /// <param name="Angle">The angle between the two rectangles</param>
        /// <param name="DeltaX">The difference between Xs</param>
        /// <param name="DeltaY">The difference between Ys</param>
        /// <param name="WithinX">Whether the other X is within the base left to right</param>
        /// <param name="WithinY">Whether the other Y is within the base top to bottom</param>
        public static void ComputeDistanceAndAngle(Rectangle BaseRect, Rectangle OtherRect, out double Dist, out double Angle, out double DeltaX, out double DeltaY, out bool WithinX, out bool WithinY)
        {
            DeltaX = ClosestToZero(OtherRect.Left - BaseRect.Left, OtherRect.Left - BaseRect.Right, OtherRect.Right - BaseRect.Left, OtherRect.Right - BaseRect.Right);
            DeltaY = ClosestToZero(BaseRect.Top - OtherRect.Top, BaseRect.Top - OtherRect.Bottom, BaseRect.Bottom - OtherRect.Top, BaseRect.Bottom - OtherRect.Bottom);
            Dist = Math.Sqrt((DeltaX * DeltaX) + (DeltaY * DeltaY));
            WithinX = (BaseRect.Left >= OtherRect.Left && BaseRect.Right < OtherRect.Right) || (OtherRect.Left >= BaseRect.Left && OtherRect.Right < BaseRect.Right) || (OtherRect.Left >= BaseRect.Left && OtherRect.Left <= BaseRect.Right);
            if (WithinX)
                DeltaX = 0;
            WithinY = (OtherRect.Top >= BaseRect.Top && OtherRect.Top <= BaseRect.Bottom) || (OtherRect.Bottom >= BaseRect.Top && OtherRect.Bottom <= BaseRect.Bottom);
            if (WithinY)
                DeltaY = 0;
            //(BaseRect.Top >= OtherRect.Top && BaseRect.Bottom < OtherRect.Bottom) || (OtherRect.Top >= BaseRect.Top && OtherRect.Bottom < BaseRect.Bottom) || (OtherRect.Top >= BaseRect.Top && OtherRect.Top <= BaseRect.Bottom);

            //WithinX = OtherRect.Left >= BaseRect.Left && OtherRect.Right <= BaseRect.Right;
            //WithinY = OtherRect.Top >= BaseRect.Top && OtherRect.Bottom <= BaseRect.Bottom;
            Angle = 180.0 * Math.Atan2(DeltaY, DeltaX) / Math.PI;
            if (Angle < 0)
                Angle += 360;
        }

        /// <summary>
        /// Determine the closest points to zero
        /// </summary>
        /// <param name="InNumbers"></param>
        /// <returns></returns>
        private static double ClosestToZero(params double[] InNumbers)
        {
            for (int a = 1; a < InNumbers.Length; a++)
                if (Math.Abs(InNumbers[a]) < Math.Abs(InNumbers[0]))
                    InNumbers[0] = InNumbers[a];
            return InNumbers[0];
        }

        /// <summary>
        /// Add an element's position to our queue
        /// </summary>
        /// <param name="PositionXml"></param>
        /// <param name="PositionLocation"></param>
        /// <param name="PositionElement"></param>
        /// <param name="xElem"></param>
        /// <param name="Location"></param>
        /// <param name="BaseElement"></param>
        private static void AddElementPosition(List<XmlElement> PositionXml, List<Object> PositionLocation, List<Object[]> PositionElement, XmlElement xElem, Object Location, Object BaseElement)
        {
            PositionXml.Add(xElem);
            PositionLocation.Add(Location);
            PositionElement.Add(new Object[] { BaseElement });
        }
        /// <summary>
        /// Add an element's position to our queue
        /// </summary>
        /// <param name="PositionXml"></param>
        /// <param name="PositionLocation"></param>
        /// <param name="PositionElement"></param>
        /// <param name="xElem"></param>
        /// <param name="Location"></param>
        /// <param name="BaseElement"></param>
        /// <param name="OtherElement"></param>
        private static void AddElementPosition(List<XmlElement> PositionXml, List<Object> PositionLocation, List<Object[]> PositionElement, XmlElement xElem, Object Location, Object BaseElement, Object OtherElement)
        {
            PositionXml.Add(xElem);
            PositionLocation.Add(Location);
            PositionElement.Add(new Object[] { BaseElement, OtherElement });
        }
    }
}